package service

import (
	"context"
	"encoding/json"
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/go-redsync/redsync/v4"
	"time"
	"ylm.sl/common/biz"
	"ylm.sl/common/constant"
	"ylm.sl/common/enums"
	"ylm.sl/common/utils"
	"ylm.sl/global"
	"ylm.sl/internal/api/request"
	user2 "ylm.sl/internal/api/response"
	"ylm.sl/internal/model"
	"ylm.sl/internal/repository/dao"
)

type IUserService interface {
	Register(ctx context.Context, dto request.UserRegisterReqDTO) error
	Login(ctx context.Context, dto request.UserLoginReqDTO) (*user2.UserLoginRespDTO, error)
	GetUserByUsername(ctx context.Context, username string) (*user2.UserActualRespDTO, error)
	HasUsername(ctx context.Context, username string) (bool, error)
	Update(ctx *gin.Context, param request.UserUpdateReqDTO) error
	CheckLogin(ctx context.Context, username string, token string) (bool, error)
	Logout(ctx context.Context, username string, token string) error
}

type UserImpl struct {
	repo                                    *dao.UserDao
	userRegisterCachePenetrationBloomFilter *utils.BloomFilter
}

func (ui *UserImpl) Logout(ctx context.Context, username string, token string) error {

	if re, _ := ui.CheckLogin(ctx, username, token); re {
		global.Redis.Del(ctx, constant.LOCK_USER_REGISTER_KEY+username)
	}
	return errors.New("用户Token不存在或用户未登录")
}

func (ui *UserImpl) CheckLogin(ctx context.Context, username string, token string) (bool, error) {
	return global.Redis.HGet(ctx, constant.USER_LOGIN_KEY+username, token) != nil, nil
}

func (ui *UserImpl) Update(ctx *gin.Context, param request.UserUpdateReqDTO) error {
	var err error
	if re := ctx.MustGet(enums.UserContext).(*biz.UserInfoDTO).Username; re == param.Username {
		err = ui.repo.Update(context.Context(ctx), model.User{
			Username: param.Username,
			Password: param.Password,
			RealName: param.RealName,
			Phone:    param.Phone,
			Mail:     param.Mail,
		})
	}

	return err
}

func (ui *UserImpl) GetUserByUsername(ctx context.Context, username string) (*user2.UserActualRespDTO, error) {
	user, err := ui.repo.SelectByUsername(ctx, username)
	if err != nil {
		return nil, err
	}
	return &user2.UserActualRespDTO{
		Id:       user.Id,
		Username: user.Username,
		RealName: user.RealName,
		Phone:    user.Phone,
		Mail:     user.Mail,
	}, nil
}

func (ui *UserImpl) Register(ctx context.Context, dto request.UserRegisterReqDTO) error {
	// TODO 添加事务管理

	var err error
	if has, err := ui.HasUsername(ctx, dto.Username); err != nil || !has {
		return errors.New("USER_NAME_EXIST")
	}
	lock := global.RedSync.NewMutex(constant.LOCK_USER_REGISTER_KEY + dto.Username)
	if err := lock.TryLock(); err != nil {
		global.Log.Error(ctx, "TryLock Error: %v", err)
		return err
	}
	defer func(lock *redsync.Mutex) {
		_, err := lock.Unlock()
		if err != nil {
			global.Log.Error(ctx, "Unlock Error: %v", err)
		}
	}(lock)

	user := model.User{
		Username: dto.Username,
		Password: dto.Password,
		Mail:     dto.Mail,
		Phone:    dto.Phone,
		RealName: dto.RealName,
	}
	if err = ui.repo.Insert(ctx, user); err != nil {
		return err
	}
	// TODO 创建默认分组
	ui.userRegisterCachePenetrationBloomFilter.Add(ctx, user.Username)
	return nil
}
func (ui *UserImpl) Login(ctx context.Context, dto request.UserLoginReqDTO) (*user2.UserLoginRespDTO, error) {
	var user *model.User
	var err error
	if user, err = ui.repo.SelectByUsernameAndPassword(ctx, dto.Username, dto.Password); err != nil {
		return nil, err
	}
	hasLoginMap, err := global.Redis.HGetAll(ctx, constant.USER_LOGIN_KEY+user.Username).Result()
	if err != nil {
		global.Log.ErrContext(ctx, "UserImpl.Redis.HGetAll failed: %v", err)
	}
	if hasLoginMap != nil {
		for k, _ := range hasLoginMap {
			global.Redis.Expire(ctx, constant.USER_LOGIN_KEY+user.Username, time.Hour)
			return &user2.UserLoginRespDTO{
				Token: k,
			}, nil
		}
	}
	/**
	 * Hash
	 * Key：login_用户名
	 * Value：
	 *  Key：token标识
	 *  Val：JSON 字符串（用户信息）
	 */
	token := utils.GenerateToken()
	userByte, err := json.Marshal(user)
	if err != nil {
		global.Log.ErrContext(ctx, "UserImpl.Login failed: %v", err)
		return nil, err
	}
	script := `
		-- 设置单个哈希字段
		redis.call("HSET", KEYS[1], ARGV[1], ARGV[2])
		
		-- 设置过期时间
		redis.call("EXPIRE", KEYS[1], tonumber(ARGV[3]))
		return 1`
	err = global.Redis.Eval(ctx, script,
		[]string{constant.USER_LOGIN_KEY + user.Username},
		token, string(userByte),
		time.Hour/time.Millisecond).Err()
	if err != nil {
		global.Log.ErrContext(ctx, "UserImpl.Login redis Set: %v", err)
		return nil, err
	}
	return &user2.UserLoginRespDTO{
		Token: token,
	}, nil
}

func (ui *UserImpl) HasUsername(ctx context.Context, username string) (bool, error) {
	return !ui.userRegisterCachePenetrationBloomFilter.Contains(ctx, username), nil
}
func NewUserService(repo *dao.UserDao, userRegisterCachePenetrationBloomFilter *utils.BloomFilter) IUserService {
	userService := &UserImpl{repo: repo, userRegisterCachePenetrationBloomFilter: userRegisterCachePenetrationBloomFilter}
	return userService
}
